#include "stdafx.h"
#include "Renderer.h"

#include "Exception.h"

namespace Cvekas {

Batch::Batch()
	:
priority(0),
batch_id(0),
vb_id(Resource::INVALID_ID),
ib_id(Resource::INVALID_ID),
effect_id(Resource::INVALID_ID),
technique(0),
pass(Effect::INVALID_PASS),
start_index(0),
tri_count(0),
owner(NULL)
{
}

Batch::Batch(const Batch& batch)
	:
priority(batch.priority),
batch_id(batch.batch_id),
vb_id(batch.vb_id),
ib_id(batch.ib_id),
effect_id(batch.effect_id),
technique(batch.technique),
pass(batch.pass),
start_index(batch.start_index),
tri_count(batch.tri_count),
owner(batch.owner)
{
}

const Batch& Batch::operator=(const Batch& batch)
{
	priority = batch.priority;
	batch_id = batch.batch_id;
	vb_id = batch.vb_id;
	ib_id = batch.ib_id;
	effect_id = batch.effect_id;
	technique = batch.technique;
	pass = batch.pass;
	start_index = batch.start_index;
	tri_count = batch.tri_count;
	owner = batch.owner;

	return *this;
}

bool Batch::operator<(const Batch& batch) const
{
	if(priority != batch.priority)
		return priority < batch.priority;

	if(effect_id != batch.effect_id)
		return effect_id < batch.effect_id;

	if(technique != batch.technique)
		return technique < batch.technique;

	if(pass != batch.pass)
		return pass < batch.pass;

	if(vb_id != batch.vb_id)
		return vb_id < batch.vb_id;

	return ib_id < batch.ib_id; 
}

Renderer::Renderer(D3DDevicePtr device, EffectManagerPtr effect_manager, GpuBufferManagerPtr gpu_buffer_manager)
	:
device(device),
effect_manager(effect_manager),
gpu_buffer_manager(gpu_buffer_manager)
{
	memset(&last_stats, 0, sizeof(last_stats));

	active_vb = active_ib = active_effect = Resource::INVALID_ID;
	active_vb_start_index = active_technique = 0;
	active_pass = Effect::INVALID_PASS;
	reset();
}

Renderer::~Renderer()
{
}

void Renderer::prepare(Renderable& renderable)
{
	is_sorted = false;

	Batch* new_batches;

	unsigned int new_count = renderable.getBatches(&new_batches);

	if(batch_count + new_count >= max_batches)
		throw EXCEPTION("Too many batches!");

	for(unsigned int i = 0; i < new_count; ++i)
	{
		batches[batch_count++] = new_batches[i];
	}
}

void Renderer::prepare(Batch& batch)
{
	is_sorted = false;

	if(batch_count+1 >= max_batches)
		throw EXCEPTION("Too many batches!");

	batches[batch_count++] = batch;
}

void Renderer::render(uint16 min_priority, uint16 max_priority)
{
	EffectPtr effect_ptr;
	VertexBufferPtr vb_ptr;
	IndexBufferPtr ib_ptr;
	bool use_ib;

	if(!is_sorted)
		sort();

	for(uint i = 0; i < batch_count; ++i)
	{
		if(min_priority != MAX_UINT16 || max_priority != MAX_UINT16)
			if(batches[i].priority < min_priority || batches[i].priority > max_priority)
				continue;

		stats.batch_count++;

		Batch& batch = batches[i];

		// Set effect
		if(active_effect != batch.effect_id || active_technique != batch.technique)
		{
			stats.effect_changes++;
			
			EffectPtr effect = effect_manager->get(batch.effect_id);

			if(bool(effect_ptr))
			{
				effect_ptr->endPass();
				effect_ptr->end();
				if(active_technique != 0)
					effect_ptr->setTechnique(0);
			}

			active_effect = batch.effect_id;
			active_technique = Effect::INVALID_TECHNIQUE;
			active_pass = Effect::INVALID_PASS;

			effect_ptr = effect;

			// Set technique
			if(active_technique != batch.technique)
			{
				effect_ptr->setTechnique(batch.technique);
				active_technique = batch.technique;
			}

			effect_ptr->begin();
		}

		// Set pass
		if(active_pass != batch.pass)
		{
			stats.pass_changes++;

			if(active_pass != Effect::INVALID_PASS)
			{
				effect_ptr->endPass();
			}

			active_pass = batch.pass;
			effect_ptr->beginPass(active_pass);
		}

		use_ib = batch.ib_id != Resource::INVALID_ID;

		// Set vertex buffer
		vb_ptr = gpu_buffer_manager->getVertexBuffer(batch.vb_id);
		uint size = vb_ptr->getVertexFormat().getSize();
		if(active_vb != batch.vb_id)
		{
			stats.vb_changes++;
			active_vb = batch.vb_id;
						
			if(use_ib)
			{
				E_E(device->SetStreamSource(0, vb_ptr->getD3DVertexBuffer(), 0, size));
			}
			else
			{
				active_vb_start_index = size * batch.start_index;
				E_E(device->SetStreamSource(0, vb_ptr->getD3DVertexBuffer(), active_vb_start_index, size));
			}

			E_E(device->SetVertexDeclaration(vb_ptr->getVertexFormat().getD3DVertexDeclaration(device, 0)));
		}
		else if(!use_ib && active_vb_start_index != size * batch.start_index)
		{
			stats.vb_changes++;
			active_vb_start_index = size * batch.start_index;
			E_E(device->SetStreamSource(0, vb_ptr->getD3DVertexBuffer(), active_vb_start_index, size));
		}

		// Set index buffer
		if(active_ib != batch.ib_id && use_ib)
		{
			stats.ib_changes++;
			active_ib = batch.ib_id;

			ib_ptr = gpu_buffer_manager->getIndexBuffer(active_ib);

			E_E(device->SetIndices(ib_ptr->getD3DIndexBuffer()));
		}

		// Render
		if(batch.owner)
			batch.owner->onPreRender(batch.batch_id);
		
		stats.triangle_count += batch.tri_count;

		if(use_ib)
		{
			E_E(device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, vb_ptr->getVertexCount(), batch.start_index, batch.tri_count));
		}
		else
		{
			E_E(device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, batch.tri_count));
		}
	}

	if(bool(effect_ptr))
	{
		effect_ptr->endPass();
		effect_ptr->end();
	}

	active_vb = active_ib = active_effect = Resource::INVALID_ID;
	active_vb_start_index = active_technique = 0;
	active_pass = Effect::INVALID_PASS;
}

void Renderer::endFrame()
{
	reset();
}

const FrameStats& Renderer::getStatistics()
{
	return last_stats;
}

void Renderer::sort()
{
	std::sort(batches.begin(), batches.begin() + batch_count);
	is_sorted = true;
}

void Renderer::reset()
{
	last_stats = stats;
	memset(&stats, 0, sizeof(stats));

	batch_count = 0;
	is_sorted = false;
}

} // namespace